1   /*
2    * Copyright (C) 2009 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect.testing.google;
18  
19  import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES;
20  import static com.google.common.collect.testing.features.CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION;
21  import static com.google.common.collect.testing.features.CollectionFeature.RESTRICTS_ELEMENTS;
22  import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD;
23  import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE;
24  import static com.google.common.collect.testing.features.CollectionSize.SEVERAL;
25  import static com.google.common.collect.testing.features.CollectionSize.ZERO;
26  
27  import com.google.common.annotations.GwtCompatible;
28  import com.google.common.annotations.GwtIncompatible;
29  import com.google.common.collect.Multiset;
30  import com.google.common.collect.Multiset.Entry;
31  import com.google.common.collect.testing.Helpers;
32  import com.google.common.collect.testing.features.CollectionFeature;
33  import com.google.common.collect.testing.features.CollectionSize;
34  
35  import java.lang.reflect.Method;
36  import java.util.Arrays;
37  import java.util.ConcurrentModificationException;
38  import java.util.Iterator;
39  import java.util.List;
40  
41  /**
42   * Common superclass for {@link MultisetSetCountUnconditionallyTester} and
43   * {@link MultisetSetCountConditionallyTester}. It is used by those testers to
44   * test calls to the unconditional {@code setCount()} method and calls to the
45   * conditional {@code setCount()} method when the expected present count is
46   * correct.
47   *
48   * @author Chris Povirk
49   */
50  @GwtCompatible(emulated = true)
51  public abstract class AbstractMultisetSetCountTester<E>
52      extends AbstractMultisetTester<E> {
53    /*
54     * TODO: consider adding MultisetFeatures.SUPPORTS_SET_COUNT. Currently we
55     * assume that using setCount() to increase the count is permitted iff add()
56     * is permitted and similarly for decrease/remove(). We assume that a
57     * setCount() no-op is permitted if either add() or remove() is permitted,
58     * though we also allow it to "succeed" if neither is permitted.
59     */
60  
61    private void assertSetCount(E element, int count) {
62      setCountCheckReturnValue(element, count);
63  
64      assertEquals(
65          "multiset.count() should return the value passed to setCount()",
66          count, getMultiset().count(element));
67  
68      int size = 0;
69      for (Multiset.Entry<E> entry : getMultiset().entrySet()) {
70        size += entry.getCount();
71      }
72      assertEquals(
73          "multiset.size() should be the sum of the counts of all entries",
74          size, getMultiset().size());
75    }
76  
77    /**
78     * Call the {@code setCount()} method under test, and check its return value.
79     */
80    abstract void setCountCheckReturnValue(E element, int count);
81  
82    /**
83     * Call the {@code setCount()} method under test, but do not check its return
84     * value. Callers should use this method over
85     * {@link #setCountCheckReturnValue(Object, int)} when they expect
86     * {@code setCount()} to throw an exception, as checking the return value
87     * could produce an incorrect error message like
88     * "setCount() should return the original count" instead of the message passed
89     * to a later invocation of {@code fail()}, like "setCount should throw
90     * UnsupportedOperationException."
91     */
92    abstract void setCountNoCheckReturnValue(E element, int count);
93  
94    private void assertSetCountIncreasingFailure(E element, int count) {
95      try {
96        setCountNoCheckReturnValue(element, count);
97        fail("a call to multiset.setCount() to increase an element's count "
98            + "should throw");
99      } catch (UnsupportedOperationException expected) {
100     }
101   }
102 
103   private void assertSetCountDecreasingFailure(E element, int count) {
104     try {
105       setCountNoCheckReturnValue(element, count);
106       fail("a call to multiset.setCount() to decrease an element's count "
107           + "should throw");
108     } catch (UnsupportedOperationException expected) {
109     }
110   }
111 
112   // Unconditional setCount no-ops.
113 
114   private void assertZeroToZero() {
115     assertSetCount(samples.e3, 0);
116   }
117 
118   private void assertOneToOne() {
119     assertSetCount(samples.e0, 1);
120   }
121 
122   private void assertThreeToThree() {
123     initThreeCopies();
124     assertSetCount(samples.e0, 3);
125   }
126 
127   @CollectionFeature.Require(SUPPORTS_ADD)
128   public void testSetCount_zeroToZero_addSupported() {
129     assertZeroToZero();
130   }
131 
132   @CollectionFeature.Require(SUPPORTS_REMOVE)
133   public void testSetCount_zeroToZero_removeSupported() {
134     assertZeroToZero();
135   }
136 
137   @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
138   public void testSetCount_zeroToZero_unsupported() {
139     try {
140       assertZeroToZero();
141     } catch (UnsupportedOperationException tolerated) {
142     }
143   }
144 
145   @CollectionSize.Require(absent = ZERO)
146   @CollectionFeature.Require(SUPPORTS_ADD)
147   public void testSetCount_oneToOne_addSupported() {
148     assertOneToOne();
149   }
150 
151   @CollectionSize.Require(absent = ZERO)
152   @CollectionFeature.Require(SUPPORTS_REMOVE)
153   public void testSetCount_oneToOne_removeSupported() {
154     assertOneToOne();
155   }
156 
157   @CollectionSize.Require(absent = ZERO)
158   @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
159   public void testSetCount_oneToOne_unsupported() {
160     try {
161       assertOneToOne();
162     } catch (UnsupportedOperationException tolerated) {
163     }
164   }
165 
166   @CollectionSize.Require(SEVERAL)
167   @CollectionFeature.Require(SUPPORTS_ADD)
168   public void testSetCount_threeToThree_addSupported() {
169     assertThreeToThree();
170   }
171 
172   @CollectionSize.Require(SEVERAL)
173   @CollectionFeature.Require(SUPPORTS_REMOVE)
174   public void testSetCount_threeToThree_removeSupported() {
175     assertThreeToThree();
176   }
177 
178   @CollectionSize.Require(SEVERAL)
179   @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
180   public void testSetCount_threeToThree_unsupported() {
181     try {
182       assertThreeToThree();
183     } catch (UnsupportedOperationException tolerated) {
184     }
185   }
186 
187   // Unconditional setCount size increases:
188 
189   @CollectionFeature.Require(SUPPORTS_ADD)
190   public void testSetCount_zeroToOne_supported() {
191     assertSetCount(samples.e3, 1);
192   }
193 
194   @CollectionFeature.Require({SUPPORTS_ADD,
195       FAILS_FAST_ON_CONCURRENT_MODIFICATION})
196   public void testSetCountZeroToOneConcurrentWithIteration() {
197     try {
198       Iterator<E> iterator = collection.iterator();
199       assertSetCount(samples.e3, 1);
200       iterator.next();
201       fail("Expected ConcurrentModificationException");
202     } catch (ConcurrentModificationException expected) {
203       // success
204     }
205   }
206 
207   @CollectionFeature.Require({SUPPORTS_ADD,
208       FAILS_FAST_ON_CONCURRENT_MODIFICATION})
209   public void testSetCountZeroToOneConcurrentWithEntrySetIteration() {
210     try {
211       Iterator<Entry<E>> iterator = getMultiset().entrySet().iterator();
212       assertSetCount(samples.e3, 1);
213       iterator.next();
214       fail("Expected ConcurrentModificationException");
215     } catch (ConcurrentModificationException expected) {
216       // success
217     }
218   }
219 
220   @CollectionFeature.Require(SUPPORTS_ADD)
221   public void testSetCount_zeroToThree_supported() {
222     assertSetCount(samples.e3, 3);
223   }
224 
225   @CollectionSize.Require(absent = ZERO)
226   @CollectionFeature.Require(SUPPORTS_ADD)
227   public void testSetCount_oneToThree_supported() {
228     assertSetCount(samples.e0, 3);
229   }
230 
231   @CollectionFeature.Require(absent = SUPPORTS_ADD)
232   public void testSetCount_zeroToOne_unsupported() {
233     assertSetCountIncreasingFailure(samples.e3, 1);
234   }
235 
236   @CollectionFeature.Require(absent = SUPPORTS_ADD)
237   public void testSetCount_zeroToThree_unsupported() {
238     assertSetCountIncreasingFailure(samples.e3, 3);
239   }
240 
241   @CollectionSize.Require(absent = ZERO)
242   @CollectionFeature.Require(absent = SUPPORTS_ADD)
243   public void testSetCount_oneToThree_unsupported() {
244     assertSetCountIncreasingFailure(samples.e3, 3);
245   }
246 
247   // Unconditional setCount size decreases:
248 
249   @CollectionSize.Require(absent = ZERO)
250   @CollectionFeature.Require(SUPPORTS_REMOVE)
251   public void testSetCount_oneToZero_supported() {
252     assertSetCount(samples.e0, 0);
253   }
254 
255   @CollectionFeature.Require({SUPPORTS_REMOVE,
256       FAILS_FAST_ON_CONCURRENT_MODIFICATION})
257   @CollectionSize.Require(absent = ZERO)
258   public void testSetCountOneToZeroConcurrentWithIteration() {
259     try {
260       Iterator<E> iterator = collection.iterator();
261       assertSetCount(samples.e0, 0);
262       iterator.next();
263       fail("Expected ConcurrentModificationException");
264     } catch (ConcurrentModificationException expected) {
265       // success
266     }
267   }
268 
269   @CollectionFeature.Require({SUPPORTS_REMOVE,
270       FAILS_FAST_ON_CONCURRENT_MODIFICATION})
271   @CollectionSize.Require(absent = ZERO)
272   public void testSetCountOneToZeroConcurrentWithEntrySetIteration() {
273     try {
274       Iterator<Entry<E>> iterator = getMultiset().entrySet().iterator();
275       assertSetCount(samples.e0, 0);
276       iterator.next();
277       fail("Expected ConcurrentModificationException");
278     } catch (ConcurrentModificationException expected) {
279       // success
280     }
281   }
282 
283   @CollectionSize.Require(SEVERAL)
284   @CollectionFeature.Require(SUPPORTS_REMOVE)
285   public void testSetCount_threeToZero_supported() {
286     initThreeCopies();
287     assertSetCount(samples.e0, 0);
288   }
289 
290   @CollectionSize.Require(SEVERAL)
291   @CollectionFeature.Require(SUPPORTS_REMOVE)
292   public void testSetCount_threeToOne_supported() {
293     initThreeCopies();
294     assertSetCount(samples.e0, 1);
295   }
296 
297   @CollectionSize.Require(absent = ZERO)
298   @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
299   public void testSetCount_oneToZero_unsupported() {
300     assertSetCountDecreasingFailure(samples.e0, 0);
301   }
302 
303   @CollectionSize.Require(SEVERAL)
304   @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
305   public void testSetCount_threeToZero_unsupported() {
306     initThreeCopies();
307     assertSetCountDecreasingFailure(samples.e0, 0);
308   }
309 
310   @CollectionSize.Require(SEVERAL)
311   @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
312   public void testSetCount_threeToOne_unsupported() {
313     initThreeCopies();
314     assertSetCountDecreasingFailure(samples.e0, 1);
315   }
316 
317   // setCount with nulls:
318 
319   @CollectionSize.Require(absent = ZERO)
320   @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
321   public void testSetCount_removeNull_nullSupported() {
322     initCollectionWithNullElement();
323     assertSetCount(null, 0);
324   }
325 
326   @CollectionFeature.Require(value = {SUPPORTS_ADD, ALLOWS_NULL_VALUES},
327       absent = RESTRICTS_ELEMENTS)
328   public void testSetCount_addNull_nullSupported() {
329     assertSetCount(null, 1);
330   }
331 
332   @CollectionFeature.Require(value = SUPPORTS_ADD, absent = ALLOWS_NULL_VALUES)
333   public void testSetCount_addNull_nullUnsupported() {
334     try {
335       setCountNoCheckReturnValue(null, 1);
336       fail("adding null with setCount() should throw NullPointerException");
337     } catch (NullPointerException expected) {
338     }
339   }
340 
341   @CollectionFeature.Require(ALLOWS_NULL_VALUES)
342   public void testSetCount_noOpNull_nullSupported() {
343     try {
344       assertSetCount(null, 0);
345     } catch (UnsupportedOperationException tolerated) {
346     }
347   }
348 
349   @CollectionFeature.Require(absent = ALLOWS_NULL_VALUES)
350   public void testSetCount_noOpNull_nullUnsupported() {
351     try {
352       assertSetCount(null, 0);
353     } catch (NullPointerException tolerated) {
354     } catch (UnsupportedOperationException tolerated) {
355     }
356   }
357 
358   @CollectionSize.Require(absent = ZERO)
359   @CollectionFeature.Require(ALLOWS_NULL_VALUES)
360   public void testSetCount_existingNoNopNull_nullSupported() {
361     initCollectionWithNullElement();
362     try {
363       assertSetCount(null, 1);
364     } catch (UnsupportedOperationException tolerated) {
365     }
366   }
367 
368   // Negative count.
369 
370   @CollectionFeature.Require(SUPPORTS_REMOVE)
371   public void testSetCount_negative_removeSupported() {
372     try {
373       setCountNoCheckReturnValue(samples.e3, -1);
374       fail("calling setCount() with a negative count should throw "
375           + "IllegalArgumentException");
376     } catch (IllegalArgumentException expected) {
377     }
378   }
379 
380   @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
381   public void testSetCount_negative_removeUnsupported() {
382     try {
383       setCountNoCheckReturnValue(samples.e3, -1);
384       fail("calling setCount() with a negative count should throw "
385           + "IllegalArgumentException or UnsupportedOperationException");
386     } catch (IllegalArgumentException expected) {
387     } catch (UnsupportedOperationException expected) {
388     }
389   }
390 
391   // TODO: test adding element of wrong type
392 
393   /**
394    * Returns {@link Method} instances for the {@code setCount()} tests that
395    * assume multisets support duplicates so that the test of {@code
396    * Multisets.forSet()} can suppress them.
397    */
398   @GwtIncompatible("reflection")
399   public static List<Method> getSetCountDuplicateInitializingMethods() {
400     return Arrays.asList(
401         getMethod("testSetCount_threeToThree_removeSupported"),
402         getMethod("testSetCount_threeToZero_supported"),
403         getMethod("testSetCount_threeToOne_supported"));
404   }
405 
406   @GwtIncompatible("reflection")
407   private static Method getMethod(String methodName) {
408     return Helpers.getMethod(AbstractMultisetSetCountTester.class, methodName);
409   }
410 }